package threewings;

import java.awt.Color;
import java.awt.RenderingHints.Key;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;

import com.sun.j3d.utils.universe.*;

import javax.media.j3d.*;
import javax.vecmath.*;

/**
 * Main class
 * 
 * @author millerlj. Created Oct 14, 2013.
 */
public class Main {

	// All globally accessible variables prefixed with _
	private static Camera camera;
	private static SimpleUniverse universe;
	private static Glider glider;
	private static MyKeyListener keyListener;
	private static BranchGroup chunks;
	private static LinearFog fog;
	private static Background background;

	// constants
	// Loaded range refers to how many chunks
	private static float LOADED_RANGE = 5;
	// is in the normal coordinate system

	private static float CHUNK_SIZE = 5 * 2;
	// the step between points
	private static float STEP = 0.5f;
	// Fog stuff
	private static final double FOG_FRONT_DISTANCE = 10;
	private static final double FOG_BACK_DISTANCE = 50;
	private static Color3f fogColor = new Color3f(255, 255, 255);
	// other variables
	private static long lastFrame;
	private static float timeFactor;

	/**
	 * starts the whole show off
	 * 
	 * @param args
	 */
	public static void main(String[] args) {

		setup();

	}

	/**
	 * Sets everything up
	 * 
	 */
	public static void setup() {
		// set time factor
		timeFactor = 1f;

		// Create the universe
		universe = new SimpleUniverse();

		// make glider
		glider = new Glider(universe);
		glider.setPosition(new Point3f(10, 3, 10));

		// make camera
		camera = new Camera(universe.getViewingPlatform()
				.getViewPlatformTransform());
		camera.setParentGlider(glider);

		// instantiate key listener
		keyListener = new MyKeyListener();

		// give stuff the key listener
		universe.getCanvas().addKeyListener(keyListener);
		glider.setKeyListener(keyListener);

		// Load all of the chunks
		chunks = new BranchGroup();
		chunks.setCapability(BranchGroup.ALLOW_DETACH);
		loadChunks(chunks, glider.getPosition());
		/**
		 * System.out.println("Chunks loaded before: " + chunks.numChildren());
		 * 
		 * unloadChunks(chunks, glider.getPosition());
		 * 
		 * System.out.println("Chunks loaded after : " + chunks.numChildren());
		 * /
		 **/
		// Create a light so we can see
		Color3f light1Color = new Color3f(2.0f, 2.0f, 2.0f);

		// Pointed in this direction
		Vector3f light1Direction = new Vector3f(4.0f, -7.0f, -12.0f);
		DirectionalLight light1 = new DirectionalLight(light1Color,
				light1Direction);

		// that shines 100m from the origin
		BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
				10000.0);
		light1.setInfluencingBounds(bounds);

		// add the light
		BranchGroup lights = new BranchGroup();
		lights.addChild(light1);

		// setup fog
		double front = FOG_FRONT_DISTANCE;
		double back = FOG_BACK_DISTANCE;
		fog = new LinearFog();
		fog.setColor(fogColor);
		fog.setBackDistance(FOG_BACK_DISTANCE);
		fog.setFrontDistance(FOG_FRONT_DISTANCE);
		fog.setCapability(Fog.ALLOW_COLOR_WRITE);
		fog.setCapability(LinearFog.ALLOW_DISTANCE_WRITE);
		fog.setInfluencingBounds(bounds);
		BranchGroup fogs = new BranchGroup();
		fogs.addChild(fog);

		// set background to be the same color as the fog so things dont looks
		// weird
		background = new Background();
		background.setColor(fogColor);
		background.setApplicationBounds(bounds);
		background.setCapability(Background.ALLOW_COLOR_WRITE);
		fogs.addChild(background);

		// add the group of objects to the Universe
		chunks.detach();// makes sure that chunks doesn't have an owner
		universe.addBranchGraph(chunks);
		universe.addBranchGraph(lights);
		//universe.addBranchGraph(fogs);

		// sets up the thread to update everything
		Runnable updateRunnable = new Runnable() {
			@Override
			public void run() {
				try {

					while (true) {
						update();
						Thread.sleep(10);
					}

				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};

		System.out.println("RUN");
		// set the frame time
		lastFrame = System.currentTimeMillis();
		updateRunnable.run();

	}

	/**
	 * Does updaty stuff
	 * 
	 */
	public static void update() {
		long now = System.currentTimeMillis();
		float dt = (now - lastFrame) / 10000f;

		// load all chunks that are within range, but not yet loaded
		loadChunks(chunks, glider.getPosition());
		// unload all chunks that are outside of the range
		unloadChunks(chunks, glider.getPosition());
		float base = 0.05f;
		float rate = 0.75f;
		if (keyListener.isPressed(KeyEvent.VK_SPACE)) {
			timeFactor -= base;
			timeFactor *= rate;
			timeFactor += base;
		} else {
			float diff = 1 - timeFactor;
			timeFactor = 1 - rate * diff;
		}
		if ((int) now % 5 == 0) {
			float red = (float) (Math.random() * 190);
			float blue = (float) (Math.random() * 190);
			float green = (float) (Math.random() * 190);
			fogColor = new Color3f(red, green, blue);
		}
		updateFog();
		// update the glider
		glider.update(dt * timeFactor, timeFactor);

		// update the camera
		camera.update();

		lastFrame = now;

	}

	/**
	 * TODO Put here a description of what this method does.
	 *
	 */
	private static void updateFog() {
		fog.setColor(fogColor);
		fog.setBackDistance(FOG_BACK_DISTANCE);
		fog.setFrontDistance(FOG_FRONT_DISTANCE);
		background.setColor(fogColor);
		
		
	}

	/**
	 * Opposite of unloadChunks, generates and adds chunks based on whether any
	 * new ones need to be made
	 * 
	 * @param chunks
	 */
	private static void loadChunks(BranchGroup c, Point3f pos) {
		// locate center of current chunk
		int cX = (int) ((int) (pos.x / CHUNK_SIZE) * CHUNK_SIZE + CHUNK_SIZE / 2);
		int cZ = (int) ((int) (pos.z / CHUNK_SIZE) * CHUNK_SIZE + CHUNK_SIZE / 2);

		// grab coordinates of loaded chunks
		HashMap<Point3f, Boolean> loadedChunks = new HashMap<Point3f, Boolean>();
		int L = c.numChildren();
		for (int i = 0; i < L; i++) {
			Chunk ch = (Chunk) c.getChild(i);
			loadedChunks.put(ch.getPosition(), null);
		}

		// set the max and min ranges
		float diff = LOADED_RANGE * CHUNK_SIZE - 0.5f * CHUNK_SIZE;
		float xMin = cX - diff;
		float xMax = cX + diff;
		float zMin = cZ - diff;
		float zMax = cZ + diff;

		// calculate half the chunk size so we don't have to keep doing it
		int hcs = (int) (CHUNK_SIZE / 2);

		// create holders for the chunkloaders and threads
		ArrayList<ChunkLoader> CLs = new ArrayList<ChunkLoader>();
		ArrayList<Thread> ts = new ArrayList<Thread>();
		// loop through all chunks and dispatch a chunk loader
		for (float x = xMin; x < xMax; x += CHUNK_SIZE) {
			for (float z = zMin; z < zMax; z += CHUNK_SIZE) {
				// if the chunk has already been loaded, skip it
				if (loadedChunks.containsKey(new Point3f(x, 0, z))) {
					continue;
				}

				// make a chunk loader and launch it in a new thread
				ChunkLoader cl = new ChunkLoader(x - hcs, z - hcs, x + hcs, z
						+ hcs, STEP);
				CLs.add(cl);
				ts.add(new Thread(cl));
				ts.get(ts.size() - 1).start();
			}
		}

		// if all the chunks are already loaded, leave
		if (CLs.size() == 0)
			return;

		// Wait for all the chunks to load
		for (int i = 0; i < ts.size(); i++) {
			// just hang till it's loaded, k?
			try {
				ts.get(i).join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		// load all the chunks into the terrain
		c.detach();
		// TODO copy c, put it, change original c, add c, detach copy
		for (ChunkLoader cl : CLs) {
			// c.detach();
			c.addChild(cl.getChunk());
			// universe.addBranchGraph(c);
		}
		universe.addBranchGraph(c);
	}

	/**
	 * 
	 * TODO Put here a description of what this method does.
	 * 
	 * @param sx
	 * @param sz
	 * @param ex
	 * @param ez
	 */
	@SuppressWarnings("unused")
	private void loadTerrain(float sx, float sz, float ex, float ez) {
		/*
		 * dispatch a bunch of chunk loaders join them add their results to
		 * chunks
		 */
	}

	/**
	 * 
	 * @param sx
	 * @param sz
	 * @param ex
	 * @param ez
	 * @param step
	 * @return the chunk that was generated
	 */
	public static Chunk generateChunk(float sx, float sz, float ex, float ez,
			float step) {
		Chunk chunk = new Chunk(sx, sz, ex, ez, step);
		chunk.setLoadedRange(LOADED_RANGE);
		return chunk;

	}

	/**
	 * Remove any chunks that are outside the loaded range
	 * 
	 * @param g
	 *            , group of chunks
	 */
	public static void unloadChunks(BranchGroup c, Point3f pos) {
		// makes it not live so that we can edit it

		int i = c.numChildren() - 1;
		while (i >= 0) {
			// if the selected chunk isn't in range
			if (!((Chunk) c.getChild(i)).inLoadedRangeS(LOADED_RANGE
					* CHUNK_SIZE, pos)) {
				// System.out.println("Unloading Chunk");
				// System.out.println("Chunk is compiled: " + c.isCompiled() +
				// " Chunk is part of a live group: " + c.isLive());
				c.detach();
				c.removeChild(i);
				universe.addBranchGraph(c);
			}
			i--;
		}

		// adds it back to the universe

	}
}
